Purpose:

Runs survival analysis models using splicing cluster assignment as a predictor

Usage

Uses a wrapper function (survival_analysis) from utils folder.

Setup

Packages and functions

Load packages, set directory paths and call setup script

library(tidyverse)
library(survival)
library(ggpubr)
library(ggplot2)
library(patchwork)

root_dir <- rprojroot::find_root(rprojroot::has_dir(".git"))

data_dir <- file.path(root_dir, "data")
analysis_dir <- file.path(root_dir, "analyses", "survival")
input_dir <- file.path(analysis_dir, "results")
results_dir <- file.path(analysis_dir, "results")
plot_dir <- file.path(analysis_dir, "plots")

# If the input and results directories do not exist, create it
if (!dir.exists(results_dir)) {
  dir.create(results_dir, recursive = TRUE)
}

source(file.path(analysis_dir, "util", "survival_models.R"))

Set metadata and cluster assignment file paths

metadata_file <- file.path(input_dir, "splicing_indices_with_survival.tsv")

cluster_file <- file.path(root_dir, "analyses",
                          "sample-psi-clustering", "results",
                          "sample-cluster-metadata-top-5000-events-stranded.tsv")

Wrangle data Add cluster assignment to metadata and define column lgg_group (LGG or non_LGG)

metadata <- read_tsv(metadata_file)
Rows: 684 Columns: 22
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (11): Kids_First_Biospecimen_ID, Histology, Kids_First_Participant_ID, molecular_subtype, extent_of_tumor_resection, EFS_event_type, OS_status, plo...
dbl (11): Total, AS_neg, AS_pos, AS_total, SI_Total, EFS_days, OS_days, age_at_diagnosis_days, age_at_diagnosis, OS_years, EFS_years

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
clusters <- read_tsv(cluster_file) %>%
  dplyr::rename(Kids_First_Biospecimen_ID = sample_id)
Rows: 729 Columns: 8
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sample_id, plot_group, plot_group_hex, RNA_library, molecular_subtype, plot_group_n
dbl (2): cluster, group_n

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# how many clusters?
n_clust <- length(unique(clusters$cluster))

metadata <- metadata %>%
  right_join(clusters %>% dplyr::select(Kids_First_Biospecimen_ID,
                                       cluster)) %>%
  dplyr::mutate(cluster = glue::glue("Cluster {cluster}")) %>%
  dplyr::mutate(cluster = fct_relevel(cluster,paste0("Cluster ", 1:n_clust))) %>%
  dplyr::mutate(lgg_group = case_when(
    plot_group == "Low-grade glioma" ~ "LGG",
    TRUE ~ "non-LGG"
  ))
Joining with `by = join_by(Kids_First_Biospecimen_ID)`

Define colors for clusters

# define colors for clusters
cluster_cols <- c("#B2DF8A","#E31A1C","#33A02C","#A6CEE3","#FB9A99","#FDBF6F",
                      "#CAB2D6","#FFFF99","#1F78B4","#B15928","#6A3D9A")
names(cluster_cols) <- glue::glue("Cluster {1:length(cluster_cols)}")

Generate log rank OS and EFS models with cluster assignment as predictor

# Generate kaplan meier survival models for OS and EFS, and save outputs
kap_os <- survival_analysis(
  metadata  = metadata,
  ind_var = "cluster",
  test = "kap.meier",
  metadata_sample_col = "Kids_First_Biospecimen_ID",
  days_col = "OS_days",
  status_col = "OS_status"
)
Testing model: survival::Surv(OS_days, OS_status) ~ cluster with kap.meier
readr::write_rds(kap_os,
                 file.path(results_dir, "logrank_OS_cluster_assignment.RDS"))

kap_efs <- survival_analysis(
  metadata  = metadata,
  ind_var = "cluster",
  test = "kap.meier",
  metadata_sample_col = "Kids_First_Biospecimen_ID",
  days_col = "EFS_days",
  status_col = "EFS_status"
)
Testing model: survival::Surv(EFS_days, EFS_status) ~ cluster with kap.meier
readr::write_rds(kap_efs,
                 file.path(results_dir, "logrankEFS_cluster_assignment.RDS"))

Generate KM plots

km_os_plot <- plotKM(model = kap_os,
                    variable = "cluster",
                    combined = F, 
                    title = "cluster",
                    palette = cluster_cols)
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
ggsave(file.path(plot_dir, "km_OS_cluster_assignment.pdf"),
       km_os_plot,
       width = 10, height = 8, units = "in",
       device = "pdf")
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
Ignoring unknown labels:
• colour : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's colour values.
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
km_efs_plot <- plotKM(model = kap_efs,
                    variable = "cluster",
                    combined = F, 
                    title = "cluster",
                    palette = cluster_cols)
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
ggsave(file.path(plot_dir, "km_EFS_cluster_assignment.pdf"),
       km_efs_plot,
       width = 10, height = 8, units = "in",
       device = "pdf")
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
Ignoring unknown labels:
• colour : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's colour values.
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.

Generate coxph models including extent of tumor resection, lgg group, and cluster assignment as covariates

add_model_os <- fit_save_model(metadata[!metadata$extent_of_tumor_resection %in% c("Not Reported", "Unavailable"),],
                              terms = "extent_of_tumor_resection+lgg_group+cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_OS_additive_terms_resection_lgg_group_cluster.RDS"),
                               "multivariate",
                               years_col = "OS_years",
                               status_col = "OS_status")

forest_os <- plotForest(readRDS(file.path(results_dir, "cox_OS_additive_terms_resection_lgg_group_cluster.RDS")))
`height` was translated to `width`.
Warning: Removed 3 rows containing missing values or values outside the scale range (`geom_text()`).

forest_os

ggsave(file.path(plot_dir, "forest_add_OS_resection_lgg_group_cluster_assignment.pdf"),
       forest_os,
       width = 10, height = 6, units = "in",
       device = "pdf")


add_model_efs <- fit_save_model(metadata[!metadata$extent_of_tumor_resection %in% c("Not Reported", "Unavailable"),],
                              terms = "extent_of_tumor_resection+lgg_group+cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_EFS_additive_terms_resection_lgg_group_cluster.RDS"),
                               "multivariate",
                               years_col = "EFS_years",
                               status_col = "EFS_status")

forest_efs <- plotForest(readRDS(file.path(results_dir, "cox_EFS_additive_terms_resection_lgg_group_cluster.RDS")))
`height` was translated to `width`.
Warning: Removed 3 rows containing missing values or values outside the scale range (`geom_text()`).

forest_efs

ggsave(file.path(plot_dir, "forest_add_EFS_resection_lgg_group_cluster_assignment.pdf"),
       forest_efs,
       width = 10, height = 6, units = "in",
       device = "pdf")

Generate coxph models including extent of tumor resection, plot group, and cluster assignment as covariates

add_model_os <- fit_save_model(metadata %>%
                                 filter(!extent_of_tumor_resection %in% c("Not Reported", "Unavailable")) %>%
                                 mutate(plot_group = forcats::fct_relevel(plot_group, "Low-grade glioma", after = 0)),
                              terms = "extent_of_tumor_resection+plot_group+cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_OS_additive_terms_resection_plot_group_cluster.RDS"),
                               "multivariate",
                               years_col = "OS_years",
                               status_col = "OS_status")
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights,  :
  Ran out of iterations and did not converge
forest_os <- plotForest(readRDS(file.path(results_dir, "cox_OS_additive_terms_resection_plot_group_cluster.RDS")))
`height` was translated to `width`.
Warning: Removed 3 rows containing missing values or values outside the scale range (`geom_text()`).

forest_os

ggsave(file.path(plot_dir, "forest_add_OS_resection_plot_group_cluster_assignment.pdf"),
       forest_os,
       width = 10, height = 6, units = "in",
       device = "pdf")


add_model_efs <- fit_save_model(metadata %>%
                                 filter(!extent_of_tumor_resection %in% c("Not Reported", "Unavailable")) %>%
                                 mutate(plot_group = forcats::fct_relevel(plot_group, "Low-grade glioma", after = 0)),
                              terms = "extent_of_tumor_resection+plot_group+cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_EFS_additive_terms_resection_plot_group_cluster.RDS"),
                               "multivariate",
                               years_col = "EFS_years",
                               status_col = "EFS_status")
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights,  :
  Loglik converged before variable  8 ; coefficient may be infinite. 
forest_efs <- plotForest(readRDS(file.path(results_dir, "cox_EFS_additive_terms_resection_plot_group_cluster.RDS")))
`height` was translated to `width`.
Warning: Removed 3 rows containing missing values or values outside the scale range (`geom_text()`).

forest_efs

ggsave(file.path(plot_dir, "forest_add_EFS_resection_plot_group_cluster_assignment.pdf"),
       forest_efs,
       width = 10, height = 6, units = "in",
       device = "pdf")

Generate interaction coxph models including extent of tumor resection, plot group, and cluster assignment as covariates

add_model_os <- fit_save_model(metadata %>%
                                 filter(!extent_of_tumor_resection %in% c("Not Reported", "Unavailable")) %>%
                                 mutate(plot_group = forcats::fct_relevel(plot_group, "Low-grade glioma", after = 0)),
                              terms = "extent_of_tumor_resection+plot_group*cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_OS_int_terms_resection_plot_group_cluster.RDS"),
                               "multivariate",
                               years_col = "OS_years",
                               status_col = "OS_status")
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights,  :
  Ran out of iterations and did not converge
forest_os <- plotForest(readRDS(file.path(results_dir, "cox_OS_int_terms_resection_plot_group_cluster.RDS")))
Warning in scale_x_log10(labels = function(x) format(x, scientific = FALSE)) :
  log-10 transformation introduced infinite values.
`height` was translated to `width`.
Warning: Removed 3 rows containing missing values or values outside the scale range (`geom_text()`).

forest_os

ggsave(file.path(plot_dir, "forest_int_OS_resection_plot_group_cluster_assignment.pdf"),
       forest_os,
       width = 15, height = 15, units = "in",
       device = "pdf")


add_model_efs <- fit_save_model(metadata %>%
                                 filter(!extent_of_tumor_resection %in% c("Not Reported", "Unavailable")) %>%
                                 mutate(plot_group = forcats::fct_relevel(plot_group, "Low-grade glioma", after = 0)),
                              terms = "extent_of_tumor_resection+plot_group*cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_EFS_int_terms_resection_plot_group_cluster.RDS"),
                               "multivariate",
                               years_col = "EFS_years",
                               status_col = "EFS_status")
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights,  :
  Ran out of iterations and did not converge
forest_efs <- plotForest(readRDS(file.path(results_dir, "cox_EFS_int_terms_resection_plot_group_cluster.RDS")))
`height` was translated to `width`.
Warning: Removed 3 rows containing missing values or values outside the scale range (`geom_text()`).

forest_efs

ggsave(file.path(plot_dir, "forest_int_EFS_resection_plot_group_cluster_assignment.pdf"),
       forest_efs,
       width = 15, height = 15, units = "in",
       device = "pdf")

Subset metadata for LGG, and only include clusters with >= 10 samples

lgg <- metadata %>%
  dplyr::filter(plot_group == "Low-grade glioma") %>%
  dplyr::mutate(cluster = factor(cluster)) %>%
  dplyr::mutate(mol_sub_group = fct_relevel(mol_sub_group, c("Wildtype", "BRAF V600E", "BRAF fusion",
                                                                "Other alteration", "SEGA"
                                                                )))

retain_clusters_lgg <- lgg %>%
  count(cluster) %>%
  filter(n >= 10) %>%
  pull(cluster)

lgg <- lgg %>%
  filter(cluster %in% retain_clusters_lgg) %>%
    dplyr::mutate(cluster = factor(cluster))

Generate log rank OS and EFS models with cluster assignment as predictor

# Generate kaplan meier survival models for OS and EFS, and save outputs
lgg_kap_os <- survival_analysis(
  metadata  = lgg,
  ind_var = "cluster",
  test = "kap.meier",
  metadata_sample_col = "Kids_First_Biospecimen_ID",
  days_col = "OS_days",
  status_col = "OS_status"
)
Testing model: survival::Surv(OS_days, OS_status) ~ cluster with kap.meier
readr::write_rds(lgg_kap_os,
                 file.path(results_dir, "logrank_lgg_OS_cluster_assignment.RDS"))

lgg_kap_efs <- survival_analysis(
  metadata  = lgg,
  ind_var = "cluster",
  test = "kap.meier",
  metadata_sample_col = "Kids_First_Biospecimen_ID",
  days_col = "EFS_days",
  status_col = "EFS_status"
)
Testing model: survival::Surv(EFS_days, EFS_status) ~ cluster with kap.meier
readr::write_rds(lgg_kap_efs,
                 file.path(results_dir, "logrank_lgg_EFS_cluster_assignment.RDS"))

Generate LGG KM plots

km_lgg_os_plot <- plotKM(model = lgg_kap_os,
                    variable = "cluster",
                    combined = F, 
                    title = "cluster",
                    palette = cluster_cols[names(cluster_cols) %in% retain_clusters_lgg])
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
ggsave(file.path(plot_dir, "km_lgg_OS_cluster_assignment.pdf"),
       km_lgg_os_plot,
       width = 10, height = 6, units = "in",
       device = "pdf")
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
Ignoring unknown labels:
• colour : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's colour values.
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
km_lgg_efs_plot <- plotKM(model = lgg_kap_efs,
                    variable = "cluster",
                    combined = F, 
                    title = "cluster",
                    palette = cluster_cols[names(cluster_cols) %in% retain_clusters_lgg])
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
ggsave(file.path(plot_dir, "km_lgg_EFS_cluster_assignment.pdf"),
       km_lgg_efs_plot,
       width = 10, height = 6, units = "in",
       device = "pdf")
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
Ignoring unknown labels:
• colour : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's colour values.
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.

Generate coxph models including covariates extent_of_tumor_resection, mol_sub_group, and cluster, and plot

add_model_lgg_os <- fit_save_model(lgg[!lgg$extent_of_tumor_resection %in% c("Not Reported", "Unavailable"),],
                              terms = "extent_of_tumor_resection+mol_sub_group+cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_lgg_OS_additive_terms_resection_subtype_cluster.RDS"),
                               "multivariate",
                               years_col = "OS_years",
                               status_col = "OS_status")
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights,  :
  Ran out of iterations and did not converge
forest_lgg_os <- plotForest(readRDS(file.path(results_dir, "cox_lgg_OS_additive_terms_resection_subtype_cluster.RDS")))
Warning in scale_x_log10(labels = function(x) format(x, scientific = FALSE)) :
  log-10 transformation introduced infinite values.
`height` was translated to `width`.
Warning: Removed 3 rows containing missing values or values outside the scale range (`geom_text()`).

forest_lgg_os

ggsave(file.path(plot_dir, "forest_add_OS_LGG_resection_subtype_cluster_assignment.pdf"),
       forest_lgg_os,
       width = 10, height = 6, units = "in",
       device = "pdf")


# identify LGG clusters
lgg_clusters <- metadata %>%
  filter(lgg_group == "LGG") %>%
  mutate(cluster = as.integer(gsub("cluster", "", cluster))) %>%
  pull(cluster) %>%
  sort() %>%
  unique()
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `cluster = as.integer(gsub("cluster", "", cluster))`.
Caused by warning:
! NAs introduced by coercion
add_model_lgg_efs <- fit_save_model(lgg[!lgg$cluster %in% lgg_clusters & !lgg$extent_of_tumor_resection %in% c("Not Reported", "Unavailable"),],
                              terms = "extent_of_tumor_resection+mol_sub_group+cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_lgg_EFS_additive_terms_resection_subtype_cluster.RDS"),
                               "multivariate",
                               years_col = "EFS_years",
                               status_col = "EFS_status")
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights,  :
  Loglik converged before variable  6,7 ; coefficient may be infinite. 
forest_lgg_efs <- plotForest(readRDS(file.path(results_dir, "cox_lgg_EFS_additive_terms_resection_subtype_cluster.RDS")))
`height` was translated to `width`.
Warning: Removed 3 rows containing missing values or values outside the scale range (`geom_text()`).

forest_lgg_efs

ggsave(file.path(plot_dir, "forest_add_EFS_LGG_resection_subtype_cluster_assignment.pdf"),
       forest_lgg_efs,
       width = 10, height = 6, units = "in",
       device = "pdf")

Subset metadata for HGG and retain cluster with n >= 10

hgg <- metadata %>%
  dplyr::filter(plot_group %in% c("Other high-grade glioma", "Diffuse midline glioma")) %>%
  dplyr::mutate(cluster = factor(cluster)) %>%
  dplyr::mutate(mol_sub_group = fct_relevel(mol_sub_group, c("HGG, H3 wildtype", "HGG, H3 wildtype, TP53",
                                                             "DMG, H3 K28", "DMG, H3 K28, TP53",
                                                                "DHG, H3 G35", "DHG, H3 G35, TP53",
                                                                "HGG, IDH, TP53", "HGG, PXA", "HGG, PXA, TP53", 
                                                                "IHG, ALK-altered", "IHG, NTRK-altered",
                                                                "IHG, ROS1-altered"
                                                                )))
Warning: There was 1 warning in `dplyr::mutate()`.
ℹ In argument: `mol_sub_group = fct_relevel(...)`.
Caused by warning:
! 2 unknown levels in `f`: HGG, PXA, TP53 and IHG, ALK-altered
retain_clusters_hgg <- hgg %>%
  count(cluster) %>%
  filter(n >= 10) %>%
  pull(cluster)

hgg <- hgg %>%
  filter(cluster %in% retain_clusters_hgg) %>%
  dplyr::mutate(cluster = factor(cluster))

Generate HGG OS and EFS log rank models with cluster as predictor

# Generate kaplan meier survival models for OS and EFS, and save outputs
hgg_kap_os <- survival_analysis(
  metadata  = hgg,
  ind_var = "cluster",
  test = "kap.meier",
  metadata_sample_col = "Kids_First_Biospecimen_ID",
  days_col = "OS_days",
  status_col = "OS_status"
)
Testing model: survival::Surv(OS_days, OS_status) ~ cluster with kap.meier
readr::write_rds(hgg_kap_os,
                 file.path(results_dir, "logrank_hgg_OS_cluster_assignment.RDS"))

hgg_kap_efs <- survival_analysis(
  metadata  = hgg,
  ind_var = "cluster",
  test = "kap.meier",
  metadata_sample_col = "Kids_First_Biospecimen_ID",
  days_col = "EFS_days",
  status_col = "EFS_status"
)
Testing model: survival::Surv(EFS_days, EFS_status) ~ cluster with kap.meier
readr::write_rds(hgg_kap_efs,
                 file.path(results_dir, "logrank_hgg_EFS_cluster_assignment.RDS"))

Generate HGG KM plots

km_hgg_os_plot <- plotKM(model = hgg_kap_os,
                    variable = "cluster",
                    combined = F, 
                    title = "cluster",
                    palette = cluster_cols[names(cluster_cols) %in% retain_clusters_hgg])
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
ggsave(file.path(plot_dir, "km_hgg_OS_cluster_assignment.pdf"),
       km_hgg_os_plot,
       width = 10, height = 6, units = "in",
       device = "pdf")
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
Ignoring unknown labels:
• colour : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's colour values.
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
km_hgg_efs_plot <- plotKM(model = hgg_kap_efs,
                    variable = "cluster",
                    combined = F, 
                    title = "cluster",
                    palette = cluster_cols[names(cluster_cols) %in% retain_clusters_hgg])
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
ggsave(file.path(plot_dir, "km_hgg_EFS_cluster_assignment.pdf"), 
       km_hgg_efs_plot,
       width = 10, height = 6, units = "in",
       device = "pdf")
Ignoring unknown labels:
• fill : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.
Ignoring unknown labels:
• colour : ""
Warning: No shared levels found between `names(values)` of the manual scale and the data's colour values.
Warning: No shared levels found between `names(values)` of the manual scale and the data's fill values.

Generate coxph models for HGG including covariates mol_sub_group and cluster, and plot

add_model_hgg_os <- fit_save_model(hgg,
                              terms = "mol_sub_group+cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_hgg_OS_additive_terms_subtype_cluster.RDS"),
                               "multivariate",
                               years_col = "OS_years",
                               status_col = "OS_status")
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights,  :
  Loglik converged before variable  8 ; coefficient may be infinite. 
forest_hgg_os <- plotForest(readRDS(file.path(results_dir, "cox_hgg_OS_additive_terms_subtype_cluster.RDS")))
`height` was translated to `width`.
Warning: Removed 2 rows containing missing values or values outside the scale range (`geom_text()`).

forest_hgg_os

ggsave(file.path(plot_dir, "forest_add_OS_HGG_subtype_cluster_assignment.pdf"),
       forest_hgg_os,
       width = 10, height = 6, units = "in",
       device = "pdf")


add_model_hgg_efs <- fit_save_model(hgg,
                              terms = "mol_sub_group+cluster+age_at_diagnosis_days",
                               file.path(results_dir, "cox_hgg_EFS_additive_terms_subtype_cluster.RDS"),
                               "multivariate",
                               years_col = "EFS_years",
                               status_col = "EFS_status")

forest_hgg_efs <- plotForest(readRDS(file.path(results_dir, "cox_hgg_EFS_additive_terms_subtype_cluster.RDS")))
`height` was translated to `width`.
Warning: Removed 2 rows containing missing values or values outside the scale range (`geom_text()`).
ggsave(file.path(plot_dir, "forest_add_EFS_HGG_subtype_cluster_assignment.pdf"),
       forest_hgg_efs,
       width = 10, height = 6, units = "in",
       device = "pdf")

Print session info

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8   
 [6] LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Etc/UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] gtools_3.9.5    survminer_0.4.9 patchwork_1.2.0 ggpubr_0.6.0    survival_3.7-0  lubridate_1.9.4 forcats_1.0.1   stringr_1.6.0   dplyr_1.1.4    
[10] purrr_1.2.0     readr_2.1.6     tidyr_1.3.1     tibble_3.3.0    ggplot2_4.0.1   tidyverse_2.0.0

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       bslib_0.9.0        xfun_0.54          rstatix_0.7.2      lattice_0.22-7     tzdb_0.5.0         vctrs_0.6.5       
 [8] tools_4.4.0        generics_0.1.4     pkgconfig_2.0.3    Matrix_1.7-4       data.table_1.17.8  RColorBrewer_1.1-3 S7_0.2.1          
[15] lifecycle_1.0.4    compiler_4.4.0     farver_2.1.2       textshaping_1.0.4  carData_3.0-5      sass_0.4.10        htmltools_0.5.8.1 
[22] yaml_2.3.10        jquerylib_0.1.4    crayon_1.5.3       pillar_1.11.1      car_3.1-2          cachem_1.1.0       abind_1.4-5       
[29] km.ci_0.5-6        commonmark_2.0.0   tidyselect_1.2.1   digest_0.6.39      stringi_1.8.7      labeling_0.4.3     splines_4.4.0     
[36] cowplot_1.1.3      rprojroot_2.1.1    fastmap_1.2.0      grid_4.4.0         cli_3.6.5          magrittr_2.0.4     broom_1.0.10      
[43] withr_3.0.2        scales_1.4.0       backports_1.5.0    bit64_4.6.0-1      timechange_0.3.0   rmarkdown_2.30     ggtext_0.1.2      
[50] bit_4.6.0          gridExtra_2.3      ggsignif_0.6.4     ragg_1.5.0         zoo_1.8-12         hms_1.1.4          evaluate_1.0.5    
[57] knitr_1.50         KMsurv_0.1-5       markdown_1.13      survMisc_0.5.6     rlang_1.1.6        Rcpp_1.1.0         gridtext_0.1.5    
[64] xtable_1.8-4       glue_1.8.0         xml2_1.5.0         rstudioapi_0.17.1  vroom_1.6.6        jsonlite_2.0.0     R6_2.6.1          
[71] systemfonts_1.3.1 
LS0tCnRpdGxlOiAiUnVuIExHRyBhbmQgSEdHIHN1cnZpdmFsIGJ5IHNwbGljaW5nIGNsdXN0ZXIgYXNzaWdubWVudCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKYXV0aG9yOiBSeWFuIENvcmJldHQKZGF0ZTogMjAyNApwYXJhbXM6CiAgcGxvdF9jaTogVFJVRQotLS0KCioqUHVycG9zZToqKiAKClJ1bnMgc3Vydml2YWwgYW5hbHlzaXMgbW9kZWxzIHVzaW5nIHNwbGljaW5nIGNsdXN0ZXIgYXNzaWdubWVudCBhcyBhIHByZWRpY3RvcgoKIyMgVXNhZ2UgCgpVc2VzIGEgd3JhcHBlciBmdW5jdGlvbiAoYHN1cnZpdmFsX2FuYWx5c2lzYCkgZnJvbSB1dGlscyBmb2xkZXIuIAoKIyMgU2V0dXAKCiMjIyMgUGFja2FnZXMgYW5kIGZ1bmN0aW9ucwoKTG9hZCBwYWNrYWdlcywgc2V0IGRpcmVjdG9yeSBwYXRocyBhbmQgY2FsbCBzZXR1cCBzY3JpcHQKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzdXJ2aXZhbCkKbGlicmFyeShnZ3B1YnIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwYXRjaHdvcmspCgpyb290X2RpciA8LSBycHJvanJvb3Q6OmZpbmRfcm9vdChycHJvanJvb3Q6Omhhc19kaXIoIi5naXQiKSkKCmRhdGFfZGlyIDwtIGZpbGUucGF0aChyb290X2RpciwgImRhdGEiKQphbmFseXNpc19kaXIgPC0gZmlsZS5wYXRoKHJvb3RfZGlyLCAiYW5hbHlzZXMiLCAic3Vydml2YWwiKQppbnB1dF9kaXIgPC0gZmlsZS5wYXRoKGFuYWx5c2lzX2RpciwgInJlc3VsdHMiKQpyZXN1bHRzX2RpciA8LSBmaWxlLnBhdGgoYW5hbHlzaXNfZGlyLCAicmVzdWx0cyIpCnBsb3RfZGlyIDwtIGZpbGUucGF0aChhbmFseXNpc19kaXIsICJwbG90cyIpCgojIElmIHRoZSBpbnB1dCBhbmQgcmVzdWx0cyBkaXJlY3RvcmllcyBkbyBub3QgZXhpc3QsIGNyZWF0ZSBpdAppZiAoIWRpci5leGlzdHMocmVzdWx0c19kaXIpKSB7CiAgZGlyLmNyZWF0ZShyZXN1bHRzX2RpciwgcmVjdXJzaXZlID0gVFJVRSkKfQoKc291cmNlKGZpbGUucGF0aChhbmFseXNpc19kaXIsICJ1dGlsIiwgInN1cnZpdmFsX21vZGVscy5SIikpCmBgYAoKU2V0IG1ldGFkYXRhIGFuZCBjbHVzdGVyIGFzc2lnbm1lbnQgZmlsZSBwYXRocwoKYGBge3Igc2V0IHBhdGhzfQptZXRhZGF0YV9maWxlIDwtIGZpbGUucGF0aChpbnB1dF9kaXIsICJzcGxpY2luZ19pbmRpY2VzX3dpdGhfc3Vydml2YWwudHN2IikKCmNsdXN0ZXJfZmlsZSA8LSBmaWxlLnBhdGgocm9vdF9kaXIsICJhbmFseXNlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgInNhbXBsZS1wc2ktY2x1c3RlcmluZyIsICJyZXN1bHRzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAic2FtcGxlLWNsdXN0ZXItbWV0YWRhdGEtdG9wLTUwMDAtZXZlbnRzLXN0cmFuZGVkLnRzdiIpCmBgYAoKV3JhbmdsZSBkYXRhIApBZGQgY2x1c3RlciBhc3NpZ25tZW50IHRvIGBtZXRhZGF0YWAgYW5kIGRlZmluZSBjb2x1bW4gYGxnZ19ncm91cGAgKExHRyBvciBub25fTEdHKQoKYGBge3J9Cm1ldGFkYXRhIDwtIHJlYWRfdHN2KG1ldGFkYXRhX2ZpbGUpCgpjbHVzdGVycyA8LSByZWFkX3RzdihjbHVzdGVyX2ZpbGUpICU+JQogIGRwbHlyOjpyZW5hbWUoS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCA9IHNhbXBsZV9pZCkKCiMgaG93IG1hbnkgY2x1c3RlcnM/Cm5fY2x1c3QgPC0gbGVuZ3RoKHVuaXF1ZShjbHVzdGVycyRjbHVzdGVyKSkKCm1ldGFkYXRhIDwtIG1ldGFkYXRhICU+JQogIHJpZ2h0X2pvaW4oY2x1c3RlcnMgJT4lIGRwbHlyOjpzZWxlY3QoS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcikpICU+JQogIGRwbHlyOjptdXRhdGUoY2x1c3RlciA9IGdsdWU6OmdsdWUoIkNsdXN0ZXIge2NsdXN0ZXJ9IikpICU+JQogIGRwbHlyOjptdXRhdGUoY2x1c3RlciA9IGZjdF9yZWxldmVsKGNsdXN0ZXIscGFzdGUwKCJDbHVzdGVyICIsIDE6bl9jbHVzdCkpKSAlPiUKICBkcGx5cjo6bXV0YXRlKGxnZ19ncm91cCA9IGNhc2Vfd2hlbigKICAgIHBsb3RfZ3JvdXAgPT0gIkxvdy1ncmFkZSBnbGlvbWEiIH4gIkxHRyIsCiAgICBUUlVFIH4gIm5vbi1MR0ciCiAgKSkKYGBgCgpEZWZpbmUgY29sb3JzIGZvciBjbHVzdGVycwoKYGBge3J9CiMgZGVmaW5lIGNvbG9ycyBmb3IgY2x1c3RlcnMKY2x1c3Rlcl9jb2xzIDwtIGMoIiNCMkRGOEEiLCIjRTMxQTFDIiwiIzMzQTAyQyIsIiNBNkNFRTMiLCIjRkI5QTk5IiwiI0ZEQkY2RiIsCiAgICAgICAgICAgICAgICAgICAgICAiI0NBQjJENiIsIiNGRkZGOTkiLCIjMUY3OEI0IiwiI0IxNTkyOCIsIiM2QTNEOUEiKQpuYW1lcyhjbHVzdGVyX2NvbHMpIDwtIGdsdWU6OmdsdWUoIkNsdXN0ZXIgezE6bGVuZ3RoKGNsdXN0ZXJfY29scyl9IikKYGBgCgpHZW5lcmF0ZSBsb2cgcmFuayBPUyBhbmQgRUZTIG1vZGVscyB3aXRoIGNsdXN0ZXIgYXNzaWdubWVudCBhcyBwcmVkaWN0b3IKCmBgYHtyfQojIEdlbmVyYXRlIGthcGxhbiBtZWllciBzdXJ2aXZhbCBtb2RlbHMgZm9yIE9TIGFuZCBFRlMsIGFuZCBzYXZlIG91dHB1dHMKa2FwX29zIDwtIHN1cnZpdmFsX2FuYWx5c2lzKAogIG1ldGFkYXRhICA9IG1ldGFkYXRhLAogIGluZF92YXIgPSAiY2x1c3RlciIsCiAgdGVzdCA9ICJrYXAubWVpZXIiLAogIG1ldGFkYXRhX3NhbXBsZV9jb2wgPSAiS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCIsCiAgZGF5c19jb2wgPSAiT1NfZGF5cyIsCiAgc3RhdHVzX2NvbCA9ICJPU19zdGF0dXMiCikKCnJlYWRyOjp3cml0ZV9yZHMoa2FwX29zLAogICAgICAgICAgICAgICAgIGZpbGUucGF0aChyZXN1bHRzX2RpciwgImxvZ3JhbmtfT1NfY2x1c3Rlcl9hc3NpZ25tZW50LlJEUyIpKQoKa2FwX2VmcyA8LSBzdXJ2aXZhbF9hbmFseXNpcygKICBtZXRhZGF0YSAgPSBtZXRhZGF0YSwKICBpbmRfdmFyID0gImNsdXN0ZXIiLAogIHRlc3QgPSAia2FwLm1laWVyIiwKICBtZXRhZGF0YV9zYW1wbGVfY29sID0gIktpZHNfRmlyc3RfQmlvc3BlY2ltZW5fSUQiLAogIGRheXNfY29sID0gIkVGU19kYXlzIiwKICBzdGF0dXNfY29sID0gIkVGU19zdGF0dXMiCikKCnJlYWRyOjp3cml0ZV9yZHMoa2FwX2VmcywKICAgICAgICAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJsb2dyYW5rRUZTX2NsdXN0ZXJfYXNzaWdubWVudC5SRFMiKSkKYGBgCgpHZW5lcmF0ZSBLTSBwbG90cwoKYGBge3J9CmttX29zX3Bsb3QgPC0gcGxvdEtNKG1vZGVsID0ga2FwX29zLAogICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlID0gImNsdXN0ZXIiLAogICAgICAgICAgICAgICAgICAgIGNvbWJpbmVkID0gRiwgCiAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiY2x1c3RlciIsCiAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IGNsdXN0ZXJfY29scykKCmdnc2F2ZShmaWxlLnBhdGgocGxvdF9kaXIsICJrbV9PU19jbHVzdGVyX2Fzc2lnbm1lbnQucGRmIiksCiAgICAgICBrbV9vc19wbG90LAogICAgICAgd2lkdGggPSAxMCwgaGVpZ2h0ID0gOCwgdW5pdHMgPSAiaW4iLAogICAgICAgZGV2aWNlID0gInBkZiIpCgprbV9lZnNfcGxvdCA8LSBwbG90S00obW9kZWwgPSBrYXBfZWZzLAogICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlID0gImNsdXN0ZXIiLAogICAgICAgICAgICAgICAgICAgIGNvbWJpbmVkID0gRiwgCiAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiY2x1c3RlciIsCiAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IGNsdXN0ZXJfY29scykKCmdnc2F2ZShmaWxlLnBhdGgocGxvdF9kaXIsICJrbV9FRlNfY2x1c3Rlcl9hc3NpZ25tZW50LnBkZiIpLAogICAgICAga21fZWZzX3Bsb3QsCiAgICAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKYGBgCgpHZW5lcmF0ZSBjb3hwaCBtb2RlbHMgaW5jbHVkaW5nIGV4dGVudCBvZiB0dW1vciByZXNlY3Rpb24sIGxnZyBncm91cCwgYW5kIGNsdXN0ZXIgYXNzaWdubWVudCBhcyBjb3ZhcmlhdGVzCgpgYGB7cn0KYWRkX21vZGVsX29zIDwtIGZpdF9zYXZlX21vZGVsKG1ldGFkYXRhWyFtZXRhZGF0YSRleHRlbnRfb2ZfdHVtb3JfcmVzZWN0aW9uICVpbiUgYygiTm90IFJlcG9ydGVkIiwgIlVuYXZhaWxhYmxlIiksXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVybXMgPSAiZXh0ZW50X29mX3R1bW9yX3Jlc2VjdGlvbitsZ2dfZ3JvdXArY2x1c3RlcithZ2VfYXRfZGlhZ25vc2lzX2RheXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X09TX2FkZGl0aXZlX3Rlcm1zX3Jlc2VjdGlvbl9sZ2dfZ3JvdXBfY2x1c3Rlci5SRFMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtdWx0aXZhcmlhdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVhcnNfY29sID0gIk9TX3llYXJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXR1c19jb2wgPSAiT1Nfc3RhdHVzIikKCmZvcmVzdF9vcyA8LSBwbG90Rm9yZXN0KHJlYWRSRFMoZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X09TX2FkZGl0aXZlX3Rlcm1zX3Jlc2VjdGlvbl9sZ2dfZ3JvdXBfY2x1c3Rlci5SRFMiKSkpCgpmb3Jlc3Rfb3MKCmdnc2F2ZShmaWxlLnBhdGgocGxvdF9kaXIsICJmb3Jlc3RfYWRkX09TX3Jlc2VjdGlvbl9sZ2dfZ3JvdXBfY2x1c3Rlcl9hc3NpZ25tZW50LnBkZiIpLAogICAgICAgZm9yZXN0X29zLAogICAgICAgd2lkdGggPSAxMCwgaGVpZ2h0ID0gNiwgdW5pdHMgPSAiaW4iLAogICAgICAgZGV2aWNlID0gInBkZiIpCgphZGRfbW9kZWxfZWZzIDwtIGZpdF9zYXZlX21vZGVsKG1ldGFkYXRhWyFtZXRhZGF0YSRleHRlbnRfb2ZfdHVtb3JfcmVzZWN0aW9uICVpbiUgYygiTm90IFJlcG9ydGVkIiwgIlVuYXZhaWxhYmxlIiksXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVybXMgPSAiZXh0ZW50X29mX3R1bW9yX3Jlc2VjdGlvbitsZ2dfZ3JvdXArY2x1c3RlcithZ2VfYXRfZGlhZ25vc2lzX2RheXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X0VGU19hZGRpdGl2ZV90ZXJtc19yZXNlY3Rpb25fbGdnX2dyb3VwX2NsdXN0ZXIuUkRTIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibXVsdGl2YXJpYXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllYXJzX2NvbCA9ICJFRlNfeWVhcnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzX2NvbCA9ICJFRlNfc3RhdHVzIikKCmZvcmVzdF9lZnMgPC0gcGxvdEZvcmVzdChyZWFkUkRTKGZpbGUucGF0aChyZXN1bHRzX2RpciwgImNveF9FRlNfYWRkaXRpdmVfdGVybXNfcmVzZWN0aW9uX2xnZ19ncm91cF9jbHVzdGVyLlJEUyIpKSkKCmZvcmVzdF9lZnMKCmdnc2F2ZShmaWxlLnBhdGgocGxvdF9kaXIsICJmb3Jlc3RfYWRkX0VGU19yZXNlY3Rpb25fbGdnX2dyb3VwX2NsdXN0ZXJfYXNzaWdubWVudC5wZGYiKSwKICAgICAgIGZvcmVzdF9lZnMsCiAgICAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSA2LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKYGBgCgpHZW5lcmF0ZSBjb3hwaCBtb2RlbHMgaW5jbHVkaW5nIGV4dGVudCBvZiB0dW1vciByZXNlY3Rpb24sIHBsb3QgZ3JvdXAsIGFuZCBjbHVzdGVyIGFzc2lnbm1lbnQgYXMgY292YXJpYXRlcwoKYGBge3J9CmFkZF9tb2RlbF9vcyA8LSBmaXRfc2F2ZV9tb2RlbChtZXRhZGF0YSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKCFleHRlbnRfb2ZfdHVtb3JfcmVzZWN0aW9uICVpbiUgYygiTm90IFJlcG9ydGVkIiwgIlVuYXZhaWxhYmxlIikpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUocGxvdF9ncm91cCA9IGZvcmNhdHM6OmZjdF9yZWxldmVsKHBsb3RfZ3JvdXAsICJMb3ctZ3JhZGUgZ2xpb21hIiwgYWZ0ZXIgPSAwKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlcm1zID0gImV4dGVudF9vZl90dW1vcl9yZXNlY3Rpb24rcGxvdF9ncm91cCtjbHVzdGVyK2FnZV9hdF9kaWFnbm9zaXNfZGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjb3hfT1NfYWRkaXRpdmVfdGVybXNfcmVzZWN0aW9uX3Bsb3RfZ3JvdXBfY2x1c3Rlci5SRFMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtdWx0aXZhcmlhdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVhcnNfY29sID0gIk9TX3llYXJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXR1c19jb2wgPSAiT1Nfc3RhdHVzIikKCmZvcmVzdF9vcyA8LSBwbG90Rm9yZXN0KHJlYWRSRFMoZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X09TX2FkZGl0aXZlX3Rlcm1zX3Jlc2VjdGlvbl9wbG90X2dyb3VwX2NsdXN0ZXIuUkRTIikpKQoKZm9yZXN0X29zCgpnZ3NhdmUoZmlsZS5wYXRoKHBsb3RfZGlyLCAiZm9yZXN0X2FkZF9PU19yZXNlY3Rpb25fcGxvdF9ncm91cF9jbHVzdGVyX2Fzc2lnbm1lbnQucGRmIiksCiAgICAgICBmb3Jlc3Rfb3MsCiAgICAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSA2LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKCmFkZF9tb2RlbF9lZnMgPC0gZml0X3NhdmVfbW9kZWwobWV0YWRhdGEgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighZXh0ZW50X29mX3R1bW9yX3Jlc2VjdGlvbiAlaW4lIGMoIk5vdCBSZXBvcnRlZCIsICJVbmF2YWlsYWJsZSIpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKHBsb3RfZ3JvdXAgPSBmb3JjYXRzOjpmY3RfcmVsZXZlbChwbG90X2dyb3VwLCAiTG93LWdyYWRlIGdsaW9tYSIsIGFmdGVyID0gMCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXJtcyA9ICJleHRlbnRfb2ZfdHVtb3JfcmVzZWN0aW9uK3Bsb3RfZ3JvdXArY2x1c3RlcithZ2VfYXRfZGlhZ25vc2lzX2RheXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X0VGU19hZGRpdGl2ZV90ZXJtc19yZXNlY3Rpb25fcGxvdF9ncm91cF9jbHVzdGVyLlJEUyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm11bHRpdmFyaWF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyc19jb2wgPSAiRUZTX3llYXJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXR1c19jb2wgPSAiRUZTX3N0YXR1cyIpCgpmb3Jlc3RfZWZzIDwtIHBsb3RGb3Jlc3QocmVhZFJEUyhmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjb3hfRUZTX2FkZGl0aXZlX3Rlcm1zX3Jlc2VjdGlvbl9wbG90X2dyb3VwX2NsdXN0ZXIuUkRTIikpKQoKZm9yZXN0X2VmcwoKZ2dzYXZlKGZpbGUucGF0aChwbG90X2RpciwgImZvcmVzdF9hZGRfRUZTX3Jlc2VjdGlvbl9wbG90X2dyb3VwX2NsdXN0ZXJfYXNzaWdubWVudC5wZGYiKSwKICAgICAgIGZvcmVzdF9lZnMsCiAgICAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSA2LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKYGBgCgpHZW5lcmF0ZSBpbnRlcmFjdGlvbiBjb3hwaCBtb2RlbHMgaW5jbHVkaW5nIGV4dGVudCBvZiB0dW1vciByZXNlY3Rpb24sIHBsb3QgZ3JvdXAsIGFuZCBjbHVzdGVyIGFzc2lnbm1lbnQgYXMgY292YXJpYXRlcwoKYGBge3J9CmFkZF9tb2RlbF9vcyA8LSBmaXRfc2F2ZV9tb2RlbChtZXRhZGF0YSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKCFleHRlbnRfb2ZfdHVtb3JfcmVzZWN0aW9uICVpbiUgYygiTm90IFJlcG9ydGVkIiwgIlVuYXZhaWxhYmxlIikpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUocGxvdF9ncm91cCA9IGZvcmNhdHM6OmZjdF9yZWxldmVsKHBsb3RfZ3JvdXAsICJMb3ctZ3JhZGUgZ2xpb21hIiwgYWZ0ZXIgPSAwKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlcm1zID0gImV4dGVudF9vZl90dW1vcl9yZXNlY3Rpb24rcGxvdF9ncm91cCpjbHVzdGVyK2FnZV9hdF9kaWFnbm9zaXNfZGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjb3hfT1NfaW50X3Rlcm1zX3Jlc2VjdGlvbl9wbG90X2dyb3VwX2NsdXN0ZXIuUkRTIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibXVsdGl2YXJpYXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllYXJzX2NvbCA9ICJPU195ZWFycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0dXNfY29sID0gIk9TX3N0YXR1cyIpCgpmb3Jlc3Rfb3MgPC0gcGxvdEZvcmVzdChyZWFkUkRTKGZpbGUucGF0aChyZXN1bHRzX2RpciwgImNveF9PU19pbnRfdGVybXNfcmVzZWN0aW9uX3Bsb3RfZ3JvdXBfY2x1c3Rlci5SRFMiKSkpCgpmb3Jlc3Rfb3MKCmdnc2F2ZShmaWxlLnBhdGgocGxvdF9kaXIsICJmb3Jlc3RfaW50X09TX3Jlc2VjdGlvbl9wbG90X2dyb3VwX2NsdXN0ZXJfYXNzaWdubWVudC5wZGYiKSwKICAgICAgIGZvcmVzdF9vcywKICAgICAgIHdpZHRoID0gMjAsIGhlaWdodCA9IDE1LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKCmFkZF9tb2RlbF9lZnMgPC0gZml0X3NhdmVfbW9kZWwobWV0YWRhdGEgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighZXh0ZW50X29mX3R1bW9yX3Jlc2VjdGlvbiAlaW4lIGMoIk5vdCBSZXBvcnRlZCIsICJVbmF2YWlsYWJsZSIpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKHBsb3RfZ3JvdXAgPSBmb3JjYXRzOjpmY3RfcmVsZXZlbChwbG90X2dyb3VwLCAiTG93LWdyYWRlIGdsaW9tYSIsIGFmdGVyID0gMCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXJtcyA9ICJleHRlbnRfb2ZfdHVtb3JfcmVzZWN0aW9uK3Bsb3RfZ3JvdXAqY2x1c3RlcithZ2VfYXRfZGlhZ25vc2lzX2RheXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X0VGU19pbnRfdGVybXNfcmVzZWN0aW9uX3Bsb3RfZ3JvdXBfY2x1c3Rlci5SRFMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtdWx0aXZhcmlhdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVhcnNfY29sID0gIkVGU195ZWFycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0dXNfY29sID0gIkVGU19zdGF0dXMiKQoKZm9yZXN0X2VmcyA8LSBwbG90Rm9yZXN0KHJlYWRSRFMoZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X0VGU19pbnRfdGVybXNfcmVzZWN0aW9uX3Bsb3RfZ3JvdXBfY2x1c3Rlci5SRFMiKSkpCgpmb3Jlc3RfZWZzCgpnZ3NhdmUoZmlsZS5wYXRoKHBsb3RfZGlyLCAiZm9yZXN0X2ludF9FRlNfcmVzZWN0aW9uX3Bsb3RfZ3JvdXBfY2x1c3Rlcl9hc3NpZ25tZW50LnBkZiIpLAogICAgICAgZm9yZXN0X2VmcywKICAgICAgIHdpZHRoID0gMjAsIGhlaWdodCA9IDE1LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKYGBgCgoKU3Vic2V0IGBtZXRhZGF0YWAgZm9yIExHRywgYW5kIG9ubHkgaW5jbHVkZSBjbHVzdGVycyB3aXRoID49IDEwIHNhbXBsZXMKCmBgYHtyfQpsZ2cgPC0gbWV0YWRhdGEgJT4lCiAgZHBseXI6OmZpbHRlcihwbG90X2dyb3VwID09ICJMb3ctZ3JhZGUgZ2xpb21hIikgJT4lCiAgZHBseXI6Om11dGF0ZShjbHVzdGVyID0gZmFjdG9yKGNsdXN0ZXIpKSAlPiUKICBkcGx5cjo6bXV0YXRlKG1vbF9zdWJfZ3JvdXAgPSBmY3RfcmVsZXZlbChtb2xfc3ViX2dyb3VwLCBjKCJXaWxkdHlwZSIsICJCUkFGIFY2MDBFIiwgIkJSQUYgZnVzaW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJPdGhlciBhbHRlcmF0aW9uIiwgIlNFR0EiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkKCnJldGFpbl9jbHVzdGVyc19sZ2cgPC0gbGdnICU+JQogIGNvdW50KGNsdXN0ZXIpICU+JQogIGZpbHRlcihuID49IDEwKSAlPiUKICBwdWxsKGNsdXN0ZXIpCgpsZ2cgPC0gbGdnICU+JQogIGZpbHRlcihjbHVzdGVyICVpbiUgcmV0YWluX2NsdXN0ZXJzX2xnZykgJT4lCiAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBmYWN0b3IoY2x1c3RlcikpCmBgYAoKR2VuZXJhdGUgbG9nIHJhbmsgT1MgYW5kIEVGUyBtb2RlbHMgd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnQgYXMgcHJlZGljdG9yCgpgYGB7cn0KIyBHZW5lcmF0ZSBrYXBsYW4gbWVpZXIgc3Vydml2YWwgbW9kZWxzIGZvciBPUyBhbmQgRUZTLCBhbmQgc2F2ZSBvdXRwdXRzCmxnZ19rYXBfb3MgPC0gc3Vydml2YWxfYW5hbHlzaXMoCiAgbWV0YWRhdGEgID0gbGdnLAogIGluZF92YXIgPSAiY2x1c3RlciIsCiAgdGVzdCA9ICJrYXAubWVpZXIiLAogIG1ldGFkYXRhX3NhbXBsZV9jb2wgPSAiS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCIsCiAgZGF5c19jb2wgPSAiT1NfZGF5cyIsCiAgc3RhdHVzX2NvbCA9ICJPU19zdGF0dXMiCikKCnJlYWRyOjp3cml0ZV9yZHMobGdnX2thcF9vcywKICAgICAgICAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJsb2dyYW5rX2xnZ19PU19jbHVzdGVyX2Fzc2lnbm1lbnQuUkRTIikpCgpsZ2dfa2FwX2VmcyA8LSBzdXJ2aXZhbF9hbmFseXNpcygKICBtZXRhZGF0YSAgPSBsZ2csCiAgaW5kX3ZhciA9ICJjbHVzdGVyIiwKICB0ZXN0ID0gImthcC5tZWllciIsCiAgbWV0YWRhdGFfc2FtcGxlX2NvbCA9ICJLaWRzX0ZpcnN0X0Jpb3NwZWNpbWVuX0lEIiwKICBkYXlzX2NvbCA9ICJFRlNfZGF5cyIsCiAgc3RhdHVzX2NvbCA9ICJFRlNfc3RhdHVzIgopCgpyZWFkcjo6d3JpdGVfcmRzKGxnZ19rYXBfZWZzLAogICAgICAgICAgICAgICAgIGZpbGUucGF0aChyZXN1bHRzX2RpciwgImxvZ3JhbmtfbGdnX0VGU19jbHVzdGVyX2Fzc2lnbm1lbnQuUkRTIikpCmBgYAoKR2VuZXJhdGUgTEdHIEtNIHBsb3RzCgpgYGB7cn0Ka21fbGdnX29zX3Bsb3QgPC0gcGxvdEtNKG1vZGVsID0gbGdnX2thcF9vcywKICAgICAgICAgICAgICAgICAgICB2YXJpYWJsZSA9ICJjbHVzdGVyIiwKICAgICAgICAgICAgICAgICAgICBjb21iaW5lZCA9IEYsIAogICAgICAgICAgICAgICAgICAgIHRpdGxlID0gImNsdXN0ZXIiLAogICAgICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBjbHVzdGVyX2NvbHNbbmFtZXMoY2x1c3Rlcl9jb2xzKSAlaW4lIHJldGFpbl9jbHVzdGVyc19sZ2ddKQoKZ2dzYXZlKGZpbGUucGF0aChwbG90X2RpciwgImttX2xnZ19PU19jbHVzdGVyX2Fzc2lnbm1lbnQucGRmIiksCiAgICAgICBrbV9sZ2dfb3NfcGxvdCwKICAgICAgIHdpZHRoID0gMTAsIGhlaWdodCA9IDYsIHVuaXRzID0gImluIiwKICAgICAgIGRldmljZSA9ICJwZGYiKQoKa21fbGdnX2Vmc19wbG90IDwtIHBsb3RLTShtb2RlbCA9IGxnZ19rYXBfZWZzLAogICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlID0gImNsdXN0ZXIiLAogICAgICAgICAgICAgICAgICAgIGNvbWJpbmVkID0gRiwgCiAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiY2x1c3RlciIsCiAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IGNsdXN0ZXJfY29sc1tuYW1lcyhjbHVzdGVyX2NvbHMpICVpbiUgcmV0YWluX2NsdXN0ZXJzX2xnZ10pCgpnZ3NhdmUoZmlsZS5wYXRoKHBsb3RfZGlyLCAia21fbGdnX0VGU19jbHVzdGVyX2Fzc2lnbm1lbnQucGRmIiksCiAgICAgICBrbV9sZ2dfZWZzX3Bsb3QsCiAgICAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSA2LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKCmBgYAoKR2VuZXJhdGUgY294cGggbW9kZWxzIGluY2x1ZGluZyBjb3ZhcmlhdGVzIGBleHRlbnRfb2ZfdHVtb3JfcmVzZWN0aW9uYCwgYG1vbF9zdWJfZ3JvdXBgLCBhbmQgYGNsdXN0ZXJgLCBhbmQgcGxvdAoKYGBge3J9CmFkZF9tb2RlbF9sZ2dfb3MgPC0gZml0X3NhdmVfbW9kZWwobGdnWyFsZ2ckZXh0ZW50X29mX3R1bW9yX3Jlc2VjdGlvbiAlaW4lIGMoIk5vdCBSZXBvcnRlZCIsICJVbmF2YWlsYWJsZSIpLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlcm1zID0gImV4dGVudF9vZl90dW1vcl9yZXNlY3Rpb24rbW9sX3N1Yl9ncm91cCtjbHVzdGVyK2FnZV9hdF9kaWFnbm9zaXNfZGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjb3hfbGdnX09TX2FkZGl0aXZlX3Rlcm1zX3Jlc2VjdGlvbl9zdWJ0eXBlX2NsdXN0ZXIuUkRTIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibXVsdGl2YXJpYXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllYXJzX2NvbCA9ICJPU195ZWFycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0dXNfY29sID0gIk9TX3N0YXR1cyIpCgpmb3Jlc3RfbGdnX29zIDwtIHBsb3RGb3Jlc3QocmVhZFJEUyhmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjb3hfbGdnX09TX2FkZGl0aXZlX3Rlcm1zX3Jlc2VjdGlvbl9zdWJ0eXBlX2NsdXN0ZXIuUkRTIikpKQoKZm9yZXN0X2xnZ19vcwoKZ2dzYXZlKGZpbGUucGF0aChwbG90X2RpciwgImZvcmVzdF9hZGRfT1NfTEdHX3Jlc2VjdGlvbl9zdWJ0eXBlX2NsdXN0ZXJfYXNzaWdubWVudC5wZGYiKSwKICAgICAgIGZvcmVzdF9sZ2dfb3MsCiAgICAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSA2LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKCiMgaWRlbnRpZnkgTEdHIGNsdXN0ZXJzCmxnZ19jbHVzdGVycyA8LSBtZXRhZGF0YSAlPiUKICBmaWx0ZXIobGdnX2dyb3VwID09ICJMR0ciKSAlPiUKICBtdXRhdGUoY2x1c3RlciA9IGFzLmludGVnZXIoZ3N1YigiY2x1c3RlciIsICIiLCBjbHVzdGVyKSkpICU+JQogIHB1bGwoY2x1c3RlcikgJT4lCiAgc29ydCgpICU+JQogIHVuaXF1ZSgpCgoKYWRkX21vZGVsX2xnZ19lZnMgPC0gZml0X3NhdmVfbW9kZWwobGdnWyFsZ2ckY2x1c3RlciAlaW4lIGxnZ19jbHVzdGVycyAmICFsZ2ckZXh0ZW50X29mX3R1bW9yX3Jlc2VjdGlvbiAlaW4lIGMoIk5vdCBSZXBvcnRlZCIsICJVbmF2YWlsYWJsZSIpLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlcm1zID0gImV4dGVudF9vZl90dW1vcl9yZXNlY3Rpb24rbW9sX3N1Yl9ncm91cCtjbHVzdGVyK2FnZV9hdF9kaWFnbm9zaXNfZGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjb3hfbGdnX0VGU19hZGRpdGl2ZV90ZXJtc19yZXNlY3Rpb25fc3VidHlwZV9jbHVzdGVyLlJEUyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm11bHRpdmFyaWF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyc19jb2wgPSAiRUZTX3llYXJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXR1c19jb2wgPSAiRUZTX3N0YXR1cyIpCgpmb3Jlc3RfbGdnX2VmcyA8LSBwbG90Rm9yZXN0KHJlYWRSRFMoZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X2xnZ19FRlNfYWRkaXRpdmVfdGVybXNfcmVzZWN0aW9uX3N1YnR5cGVfY2x1c3Rlci5SRFMiKSkpCgpmb3Jlc3RfbGdnX2VmcwoKZ2dzYXZlKGZpbGUucGF0aChwbG90X2RpciwgImZvcmVzdF9hZGRfRUZTX0xHR19yZXNlY3Rpb25fc3VidHlwZV9jbHVzdGVyX2Fzc2lnbm1lbnQucGRmIiksCiAgICAgICBmb3Jlc3RfbGdnX2VmcywKICAgICAgIHdpZHRoID0gMTAsIGhlaWdodCA9IDYsIHVuaXRzID0gImluIiwKICAgICAgIGRldmljZSA9ICJwZGYiKQpgYGAKClN1YnNldCBgbWV0YWRhdGFgIGZvciBIR0cgYW5kIHJldGFpbiBjbHVzdGVyIHdpdGggbiA+PSAxMAoKYGBge3J9CmhnZyA8LSBtZXRhZGF0YSAlPiUKICBkcGx5cjo6ZmlsdGVyKHBsb3RfZ3JvdXAgJWluJSBjKCJPdGhlciBoaWdoLWdyYWRlIGdsaW9tYSIsICJEaWZmdXNlIG1pZGxpbmUgZ2xpb21hIikpICU+JQogIGRwbHlyOjptdXRhdGUoY2x1c3RlciA9IGZhY3RvcihjbHVzdGVyKSkgJT4lCiAgZHBseXI6Om11dGF0ZShtb2xfc3ViX2dyb3VwID0gZmN0X3JlbGV2ZWwobW9sX3N1Yl9ncm91cCwgYygiSEdHLCBIMyB3aWxkdHlwZSIsICJIR0csIEgzIHdpbGR0eXBlLCBUUDUzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJETUcsIEgzIEsyOCIsICJETUcsIEgzIEsyOCwgVFA1MyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiREhHLCBIMyBHMzUiLCAiREhHLCBIMyBHMzUsIFRQNTMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkhHRywgSURILCBUUDUzIiwgIkhHRywgUFhBIiwgIkhHRywgUFhBLCBUUDUzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSUhHLCBBTEstYWx0ZXJlZCIsICJJSEcsIE5UUkstYWx0ZXJlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSUhHLCBST1MxLWFsdGVyZWQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkKCnJldGFpbl9jbHVzdGVyc19oZ2cgPC0gaGdnICU+JQogIGNvdW50KGNsdXN0ZXIpICU+JQogIGZpbHRlcihuID49IDEwKSAlPiUKICBwdWxsKGNsdXN0ZXIpCgpoZ2cgPC0gaGdnICU+JQogIGZpbHRlcihjbHVzdGVyICVpbiUgcmV0YWluX2NsdXN0ZXJzX2hnZykgJT4lCiAgZHBseXI6Om11dGF0ZShjbHVzdGVyID0gZmFjdG9yKGNsdXN0ZXIpKQpgYGAKCkdlbmVyYXRlIEhHRyBPUyBhbmQgRUZTIGxvZyByYW5rIG1vZGVscyB3aXRoIGNsdXN0ZXIgYXMgcHJlZGljdG9yCgpgYGB7cn0KIyBHZW5lcmF0ZSBrYXBsYW4gbWVpZXIgc3Vydml2YWwgbW9kZWxzIGZvciBPUyBhbmQgRUZTLCBhbmQgc2F2ZSBvdXRwdXRzCmhnZ19rYXBfb3MgPC0gc3Vydml2YWxfYW5hbHlzaXMoCiAgbWV0YWRhdGEgID0gaGdnLAogIGluZF92YXIgPSAiY2x1c3RlciIsCiAgdGVzdCA9ICJrYXAubWVpZXIiLAogIG1ldGFkYXRhX3NhbXBsZV9jb2wgPSAiS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCIsCiAgZGF5c19jb2wgPSAiT1NfZGF5cyIsCiAgc3RhdHVzX2NvbCA9ICJPU19zdGF0dXMiCikKCnJlYWRyOjp3cml0ZV9yZHMoaGdnX2thcF9vcywKICAgICAgICAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJsb2dyYW5rX2hnZ19PU19jbHVzdGVyX2Fzc2lnbm1lbnQuUkRTIikpCgpoZ2dfa2FwX2VmcyA8LSBzdXJ2aXZhbF9hbmFseXNpcygKICBtZXRhZGF0YSAgPSBoZ2csCiAgaW5kX3ZhciA9ICJjbHVzdGVyIiwKICB0ZXN0ID0gImthcC5tZWllciIsCiAgbWV0YWRhdGFfc2FtcGxlX2NvbCA9ICJLaWRzX0ZpcnN0X0Jpb3NwZWNpbWVuX0lEIiwKICBkYXlzX2NvbCA9ICJFRlNfZGF5cyIsCiAgc3RhdHVzX2NvbCA9ICJFRlNfc3RhdHVzIgopCgpyZWFkcjo6d3JpdGVfcmRzKGhnZ19rYXBfZWZzLAogICAgICAgICAgICAgICAgIGZpbGUucGF0aChyZXN1bHRzX2RpciwgImxvZ3JhbmtfaGdnX0VGU19jbHVzdGVyX2Fzc2lnbm1lbnQuUkRTIikpCmBgYAoKR2VuZXJhdGUgSEdHIEtNIHBsb3RzCgpgYGB7cn0Ka21faGdnX29zX3Bsb3QgPC0gcGxvdEtNKG1vZGVsID0gaGdnX2thcF9vcywKICAgICAgICAgICAgICAgICAgICB2YXJpYWJsZSA9ICJjbHVzdGVyIiwKICAgICAgICAgICAgICAgICAgICBjb21iaW5lZCA9IEYsIAogICAgICAgICAgICAgICAgICAgIHRpdGxlID0gImNsdXN0ZXIiLAogICAgICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBjbHVzdGVyX2NvbHNbbmFtZXMoY2x1c3Rlcl9jb2xzKSAlaW4lIHJldGFpbl9jbHVzdGVyc19oZ2ddKQoKZ2dzYXZlKGZpbGUucGF0aChwbG90X2RpciwgImttX2hnZ19PU19jbHVzdGVyX2Fzc2lnbm1lbnQucGRmIiksCiAgICAgICBrbV9oZ2dfb3NfcGxvdCwKICAgICAgIHdpZHRoID0gMTAsIGhlaWdodCA9IDYsIHVuaXRzID0gImluIiwKICAgICAgIGRldmljZSA9ICJwZGYiKQoKa21faGdnX2Vmc19wbG90IDwtIHBsb3RLTShtb2RlbCA9IGhnZ19rYXBfZWZzLAogICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlID0gImNsdXN0ZXIiLAogICAgICAgICAgICAgICAgICAgIGNvbWJpbmVkID0gRiwgCiAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiY2x1c3RlciIsCiAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IGNsdXN0ZXJfY29sc1tuYW1lcyhjbHVzdGVyX2NvbHMpICVpbiUgcmV0YWluX2NsdXN0ZXJzX2hnZ10pCgpnZ3NhdmUoZmlsZS5wYXRoKHBsb3RfZGlyLCAia21faGdnX0VGU19jbHVzdGVyX2Fzc2lnbm1lbnQucGRmIiksIAogICAgICAga21faGdnX2Vmc19wbG90LAogICAgICAgd2lkdGggPSAxMCwgaGVpZ2h0ID0gNiwgdW5pdHMgPSAiaW4iLAogICAgICAgZGV2aWNlID0gInBkZiIpCgpgYGAKCkdlbmVyYXRlIGNveHBoIG1vZGVscyBmb3IgSEdHIGluY2x1ZGluZyBjb3ZhcmlhdGVzIGBtb2xfc3ViX2dyb3VwYCBhbmQgYGNsdXN0ZXJgLCBhbmQgcGxvdAoKYGBge3J9CmFkZF9tb2RlbF9oZ2dfb3MgPC0gZml0X3NhdmVfbW9kZWwoaGdnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXJtcyA9ICJtb2xfc3ViX2dyb3VwK2NsdXN0ZXIrYWdlX2F0X2RpYWdub3Npc19kYXlzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGUucGF0aChyZXN1bHRzX2RpciwgImNveF9oZ2dfT1NfYWRkaXRpdmVfdGVybXNfc3VidHlwZV9jbHVzdGVyLlJEUyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm11bHRpdmFyaWF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyc19jb2wgPSAiT1NfeWVhcnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzX2NvbCA9ICJPU19zdGF0dXMiKQoKZm9yZXN0X2hnZ19vcyA8LSBwbG90Rm9yZXN0KHJlYWRSRFMoZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiY294X2hnZ19PU19hZGRpdGl2ZV90ZXJtc19zdWJ0eXBlX2NsdXN0ZXIuUkRTIikpKQoKZm9yZXN0X2hnZ19vcwoKZ2dzYXZlKGZpbGUucGF0aChwbG90X2RpciwgImZvcmVzdF9hZGRfT1NfSEdHX3N1YnR5cGVfY2x1c3Rlcl9hc3NpZ25tZW50LnBkZiIpLAogICAgICAgZm9yZXN0X2hnZ19vcywKICAgICAgIHdpZHRoID0gMTAsIGhlaWdodCA9IDYsIHVuaXRzID0gImluIiwKICAgICAgIGRldmljZSA9ICJwZGYiKQoKYWRkX21vZGVsX2hnZ19lZnMgPC0gZml0X3NhdmVfbW9kZWwoaGdnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXJtcyA9ICJtb2xfc3ViX2dyb3VwK2NsdXN0ZXIrYWdlX2F0X2RpYWdub3Npc19kYXlzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGUucGF0aChyZXN1bHRzX2RpciwgImNveF9oZ2dfRUZTX2FkZGl0aXZlX3Rlcm1zX3N1YnR5cGVfY2x1c3Rlci5SRFMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtdWx0aXZhcmlhdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVhcnNfY29sID0gIkVGU195ZWFycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0dXNfY29sID0gIkVGU19zdGF0dXMiKQoKZm9yZXN0X2hnZ19lZnMgPC0gcGxvdEZvcmVzdChyZWFkUkRTKGZpbGUucGF0aChyZXN1bHRzX2RpciwgImNveF9oZ2dfRUZTX2FkZGl0aXZlX3Rlcm1zX3N1YnR5cGVfY2x1c3Rlci5SRFMiKSkpCgpnZ3NhdmUoZmlsZS5wYXRoKHBsb3RfZGlyLCAiZm9yZXN0X2FkZF9FRlNfSEdHX3N1YnR5cGVfY2x1c3Rlcl9hc3NpZ25tZW50LnBkZiIpLAogICAgICAgZm9yZXN0X2hnZ19lZnMsCiAgICAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSA2LCB1bml0cyA9ICJpbiIsCiAgICAgICBkZXZpY2UgPSAicGRmIikKYGBgCgpQcmludCBzZXNzaW9uIGluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYA==